﻿using GenericRepositoryDemo.Domain.Abstracts.Entites;
using GenericRepositoryDemo.Domain.Abstracts.Extensions;
using GenericRepositoryDemo.Domain.Abstracts.Repositories;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace GenericRepositoryDemo.Infrastructure.Abstracts.Repositories;

public class EfCoreRepositoryBase<TDbContext, TEntity, TKey> : RepositoryBase<TEntity, TKey>
    where TDbContext : DbContext
    where TEntity : class, IEntity<TKey>
{
    private readonly TDbContext _dbContext;

    public EfCoreRepositoryBase(TDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public override async Task<TEntity> FindAsync(TKey id)
    {
        return await (await GetDbSetAsync()).FindAsync(new object[] { id });
    }
    public override async Task<TEntity> FindAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await (await GetDbSetAsync()).Where(predicate).SingleOrDefaultAsync();
    }
    public override async Task<List<TEntity>> GetListAsync()
    {
        return await (await GetDbSetAsync()).ToListAsync();
    }
    public override async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await (await GetDbSetAsync()).Where(predicate).ToListAsync();
    }
    public override async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting)
    {
        var queryable = await GetDbSetAsync();

        return await queryable
            .OrderByIf<TEntity, IQueryable<TEntity>>(!sorting.IsNullOrWhiteSpace(), sorting)
            .PageBy(skipCount, maxResultCount)
            .ToListAsync();
    }
    public override async Task<long> GetCountAsync()
    {
        return await (await GetDbSetAsync()).LongCountAsync();
    }

    public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false)
    {
        var dbContext = await GetDbContextAsync();

        var savedEntity = (await dbContext.Set<TEntity>().AddAsync(entity)).Entity;

        if (autoSave)
        {
            await dbContext.SaveChangesAsync();
        }

        return savedEntity;
    }

    public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false)
    {
        var dbContext = await GetDbContextAsync();

        dbContext.Attach(entity);

        var updatedEntity = dbContext.Update(entity).Entity;

        if (autoSave)
        {
            await dbContext.SaveChangesAsync();
        }

        return updatedEntity;
    }

    public override async Task DeleteAsync(TEntity entity, bool autoSave = false)
    {
        var dbContext = await GetDbContextAsync();

        dbContext.Set<TEntity>().Remove(entity);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync();
        }
    }
    public override async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false)
    {
        var dbContext = await GetDbContextAsync();
        var dbSet = dbContext.Set<TEntity>();

        var entities = await dbSet
            .Where(predicate)
            .ToListAsync();

        await DeleteManyAsync(entities, autoSave);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync();
        }
    }

    #region Common
    public override async Task SaveChangesAsync()
    {
        await (await GetDbContextAsync()).SaveChangesAsync();
    }

    public override async Task<IQueryable<TEntity>> WithDetailsAsync(params Expression<Func<TEntity, object>>[] propertySelectors)
    {
        return IncludeDetails(
            await GetQueryableAsync(),
            propertySelectors
        );
    }

    private static IQueryable<TEntity> IncludeDetails(
        IQueryable<TEntity> query,
        Expression<Func<TEntity, object>>[] propertySelectors)
    {
        if (!propertySelectors.IsNullOrEmpty())
        {
            foreach (var propertySelector in propertySelectors)
            {
                query = query.Include(propertySelector);
            }
        }

        return query;
    }

    public override async Task<IQueryable<TEntity>> GetQueryableAsync()
    {
        return (await GetDbSetAsync()).AsQueryable();
    }

    public virtual async Task<TDbContext> GetDbContextAsync()
    {
        return await Task.FromResult(_dbContext);
    }

    protected async Task<DbSet<TEntity>> GetDbSetAsync()
    {
        return (await GetDbContextAsync()).Set<TEntity>();
    }
    #endregion
}